home *** CD-ROM | disk | FTP | other *** search
/ AmigActive 23 / AACD 23.iso / AACD / Online / opennap / search.c < prev    next >
C/C++ Source or Header  |  2001-06-08  |  33KB  |  1,175 lines

  1. /* Copyright (C) 2000-1 drscholl@users.sourceforge.net
  2.    This is free software distributed under the terms of the
  3.    GNU Public License.  See the file COPYING for details.
  4.  
  5.    $Id: search.c,v 1.127 2001/02/15 08:39:45 drscholl Exp $ */
  6.  
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <ctype.h>
  10. #include <stdio.h>
  11. #include <limits.h>
  12. #include <arpa/inet.h>
  13. #include "opennap.h"
  14. #include "debug.h"
  15.  
  16. /* number of searches performed */
  17. unsigned int Search_Count = 0;
  18.  
  19. /* structure used when handing a search for a remote user */
  20. typedef struct
  21. {
  22.     CONNECTION *con;            /* connection to user that issused the search,
  23.                                    or the server they are connected to if
  24.                                    remote */
  25.     char   *nick;               /* user who issued the search */
  26.     char   *id;                 /* the id for this search */
  27.     short   count;              /* how many ACKS have been recieved? */
  28.     short   numServers;         /* how many servers were connected at the time
  29.                                    this search was issued? */
  30.     time_t  timestamp;          /* when the search request was issued */
  31. }
  32. DSEARCH;
  33.  
  34. static LIST *Remote_Search = 0;
  35.  
  36. /* keep a pointer to the end of the list for fast append */
  37. static LIST **Remote_Search_Tail = &Remote_Search;
  38.  
  39. /* keep count of how many searches we are waiting for */
  40. unsigned int Pending_Searches = 0;
  41.  
  42. static void
  43. search_end (CONNECTION * con, const char *id)
  44. {
  45.     if (ISUSER (con))
  46.     {
  47.         if (con->uopt->searches <= 0)
  48.         {
  49.             log ("search_end: ERROR, con->uopt->searches <= 0!!!!!!");
  50.             con->uopt->searches = 0;
  51.         }
  52.         else
  53.             con->uopt->searches--;
  54.         send_cmd (con, MSG_SERVER_SEARCH_END, "");
  55.     }
  56.     else
  57.     {
  58.         send_cmd (con, MSG_SERVER_REMOTE_SEARCH_END, "%s", id);
  59.     }
  60. }
  61.  
  62. static void
  63. free_dsearch (DSEARCH * d)
  64. {
  65.     if (d)
  66.     {
  67.         if (d->id)
  68.             FREE (d->id);
  69.         if (d->nick)
  70.             FREE (d->nick);
  71.         FREE (d);
  72.     }
  73. }
  74.  
  75. static char *
  76. generate_search_id (void)
  77. {
  78.     char   *id = MALLOC (9);
  79.     int     i;
  80.  
  81.     if (!id)
  82.     {
  83.         OUTOFMEMORY ("generate_search_id");
  84.         return 0;
  85.     }
  86.     for (i = 0; i < 8; i++)
  87.         id[i] = 'A' + (rand () % 26);
  88.     id[8] = 0;
  89.     return id;
  90. }
  91.  
  92. /* initiate a distributed search request.  `con' is where we received the
  93.  * request from (could be from a locally connected user or from another
  94.  * server.  `user' is the end-client that issued the search originally,
  95.  * `id' is the search id if receieved from a peer server, NULL if from
  96.  * a locally connected client.  `request' is the search string received from
  97.  * the client indicating what results they want.
  98.  */
  99. static int
  100. dsearch_alloc (CONNECTION * con, USER * user, const char *id,
  101.                const char *request)
  102. {
  103.     DSEARCH *dsearch;
  104.     LIST   *ptr;
  105.  
  106.     /* generate a new request structure */
  107.     dsearch = CALLOC (1, sizeof (DSEARCH));
  108.     if (!dsearch)
  109.     {
  110.         OUTOFMEMORY ("search_internal");
  111.         return -1;
  112.     }
  113.     dsearch->timestamp = global.current_time;
  114.     if (id)
  115.     {
  116.         if ((dsearch->id = STRDUP (id)) == 0)
  117.         {
  118.             OUTOFMEMORY ("search_internal");
  119.             FREE (dsearch);
  120.             return -1;
  121.         }
  122.     }
  123.     /* local client issued the search request, generate a new search id so
  124.      * that we can route the results from peer servers back to the correct
  125.      * user
  126.      */
  127.     else if ((dsearch->id = generate_search_id ()) == 0)
  128.     {
  129.         FREE (dsearch);
  130.         return -1;
  131.     }
  132.     dsearch->con = con;
  133.     if (!(dsearch->nick = STRDUP (user->nick)))
  134.     {
  135.         OUTOFMEMORY ("search_internal");
  136.         free_dsearch (dsearch);
  137.         return -1;
  138.     }
  139.  
  140.     /* keep track of how many replies we expect back */
  141.     dsearch->numServers = list_count (Servers);
  142.     /* if we recieved this from a server, we expect 1 less reply since
  143.        we don't send the search request back to the server that issued
  144.        it */
  145.     if (ISSERVER (con))
  146.         dsearch->numServers--;
  147.  
  148.     ptr = CALLOC (1, sizeof (LIST));
  149.     if (!ptr)
  150.     {
  151.         OUTOFMEMORY ("search_internal");
  152.         free_dsearch (dsearch);
  153.         return -1;
  154.     }
  155.     ptr->data = dsearch;
  156.  
  157.     /* append search to the tail of the list. */
  158.     *Remote_Search_Tail = ptr;
  159.     Remote_Search_Tail = &ptr->next;
  160.  
  161.     Pending_Searches++;
  162.  
  163.     /* pass this message to all servers EXCEPT the one we recieved
  164.        it from (if this was a remote search) */
  165.     pass_message_args (con, MSG_SERVER_REMOTE_SEARCH, "%s %s %s",
  166.                        user->nick, dsearch->id, request);
  167.  
  168.     return 0;
  169. }
  170.  
  171. #ifndef ROUTING_ONLY
  172.  
  173. /* parameters for searching */
  174. typedef struct
  175. {
  176.     CONNECTION *con;            /* connection for user that issued search */
  177.     USER   *user;               /* user that issued the search */
  178.     int     minbitrate;
  179.     int     maxbitrate;
  180.     int     minfreq;
  181.     int     maxfreq;
  182.     int     minspeed;
  183.     int     maxspeed;
  184.     unsigned int minsize;
  185.     unsigned int maxsize;
  186.     int     minduration;
  187.     int     maxduration;
  188.     int     type;               /* -1 means any type */
  189.     char   *id;                 /* if doing a remote search */
  190. }
  191. SEARCH;
  192.  
  193. /* returns nonzero if there is already the token specified by `s' in the
  194.    list */
  195. static int
  196. duplicate (LIST * list, const char *s)
  197. {
  198.     ASSERT (s != 0);
  199.     for (; list; list = list->next)
  200.     {
  201.         ASSERT (list->data != 0);
  202.         if (!strcmp (s, list->data))
  203.             return 1;
  204.     }
  205.     return 0;
  206. }
  207.  
  208. /* consider the apostrophe to be part of the word since it doesn't make
  209.    sense on its own */
  210. #define WORD_CHAR(c) \
  211.         (isalnum((unsigned char)(c))||(c)=='\''||(unsigned char)(c) > 128)
  212.  
  213. /* return a list of word tokens from the input string.  if excludes != NULL,
  214.  * consider words prefixed with a minus (`-') to be excluded words, and
  215.  * return them in a separate list
  216.  */
  217. LIST   *
  218. tokenize (char *s, LIST ** exclude_list)
  219. {
  220.     LIST   *r = 0, **cur = &r;
  221.     char   *ptr;
  222.     int     exclude;
  223.  
  224.     /* there may be existing entries, find the end of the list */
  225.     if (exclude_list)
  226.         while (*exclude_list)
  227.             exclude_list = &(*exclude_list)->next;
  228.  
  229.     while (*s)
  230.     {
  231.         exclude = 0;
  232.         while (*s && !WORD_CHAR (*s))
  233.         {
  234.             /* XXX this will catch stupid things like  "- -fast" or
  235.              * "- slow", but we'll make it fast for the basic case instead
  236.              * of worrying about it.
  237.              */
  238.             if (exclude_list && *s == '-')
  239.                 exclude = 1;
  240.             s++;
  241.         }
  242.         ptr = s;
  243.         while (WORD_CHAR (*ptr))
  244.             ptr++;
  245.         if (*ptr)
  246.             *ptr++ = 0;
  247.         strlower (s);           /* convert to lower case to save time */
  248.  
  249.         /* don't bother with common words, if there is more than 5,000 of
  250.            any of these it doesnt do any good for the search engine because
  251.            it won't match on them.  its doubtful that these would narrow
  252.            searches down any even after the selection of the bin to search */
  253.         /* new dynamic table from config file */
  254.         if (is_filtered (s))
  255.         {
  256.             s = ptr;
  257.             continue;
  258.         }
  259.  
  260.         /* don't add duplicate tokens to the list.  this will cause searches
  261.            on files that have the same token more than once to show up how
  262.            ever many times the token appears in the filename */
  263.         if ((!exclude && duplicate (r, s)) ||
  264.             (exclude && duplicate (*exclude_list, s)))
  265.         {
  266.             s = ptr;
  267.             continue;
  268.         }
  269.  
  270.         if (exclude)
  271.         {
  272.             *exclude_list = CALLOC (1, sizeof (LIST));
  273.             if (!*exclude_list)
  274.             {
  275.                 OUTOFMEMORY ("tokenize");
  276.                 return r;
  277.             }
  278.             (*exclude_list)->data = s;
  279.             exclude_list = &(*exclude_list)->next;
  280.         }
  281.         else
  282.         {
  283.             *cur = CALLOC (1, sizeof (LIST));
  284.             if (!*cur)
  285.             {
  286.                 OUTOFMEMORY ("tokenize");
  287.                 return r;
  288.             }
  289.             (*cur)->data = s;
  290.             cur = &(*cur)->next;
  291.         }
  292.  
  293.         s = ptr;
  294.     }
  295.     return r;
  296. }
  297.  
  298. void
  299. free_datum (DATUM * d)
  300. {
  301.     LIST   *list, *tmp;
  302.     FLIST  *flist;
  303.  
  304.     /* remove this datum from the lists for each keyword it is indexed
  305.      * under
  306.      */
  307.     list = d->tokens;
  308.     while (list)
  309.     {
  310.         flist = list->data;
  311.         ASSERT (validate_flist (flist));
  312.         flist->list = list_delete (flist->list, d);
  313.         flist->count--;
  314.         /* if there are no more files in this bin, erase it */
  315.         if (flist->count == 0)
  316.         {
  317.             ASSERT (flist->list == 0);
  318.             hash_remove (File_Table, flist->key);
  319.             FREE (flist->key);
  320.             FREE (flist);
  321.         }
  322.         tmp = list;
  323.         list = list->next;
  324.         FREE (tmp);
  325.     }
  326.  
  327. #if RESUME
  328.     flist = hash_lookup (MD5, d->hash);
  329.     if (flist)
  330.     {
  331.         ASSERT (validate_flist (flist));
  332.         flist->list = list_delete (flist->list, d);
  333.         flist->count--;
  334.         /* if there are no more files in this bin, erase it */
  335.         if (flist->count == 0)
  336.         {
  337.             ASSERT (flist->list == 0);
  338.             hash_remove (MD5, flist->key);
  339.             FREE (flist->key);
  340.             FREE (flist);
  341.         }
  342.     }
  343.     else
  344.         log ("free_datum: error, no hash entry for file %s", d->filename);
  345.     FREE (d->hash);
  346. #endif
  347.  
  348.     FREE (d->filename);
  349.     FREE (d);
  350. }
  351.  
  352. static int
  353. fdb_search (LIST * contains, LIST * excludes, int maxhits, SEARCH * crit)
  354. {
  355.     LIST   *words = 0;          /* matched words */
  356.     LIST   *exclude_words = 0;  /* words NOT to match */
  357.     LIST   *ptok;
  358.     FLIST  *flist = 0, *tmp;
  359.     DATUM  *d;
  360.     int     hits = 0;
  361.     LIST   *list;               /* temp pointer for creation of `words' list */
  362.     LIST   *pWords;             /* iteration pointer for `words' list */
  363.     int     is_match;
  364.  
  365.     Search_Count++;
  366.  
  367.     if (!contains)
  368.     {
  369.         /* this shouldn't happen because we catch this condition down where
  370.          * fdb_search() is called and report it back to the user
  371.          */
  372.         log ("fdb_search: error, tokens==NULL");
  373.         return 0;
  374.     }
  375.  
  376.     /* find the file list with the fewest files in it */
  377.     for (ptok = contains; ptok; ptok = ptok->next)
  378.     {
  379.         tmp = hash_lookup (File_Table, ptok->data);
  380.         if (!tmp)
  381.         {
  382.             /* if there is no entry for this word in the hash table, then
  383.                we know there are no matches */
  384.  
  385.             return 0;
  386.         }
  387.         ASSERT (validate_flist (tmp));
  388.         /* keep track of the flist with the fewest entries in it.  we use
  389.          * this below to refine the search.  we use the smallest subset
  390.          * of possible matches to narrow the search down.
  391.          */
  392.         if (!flist || tmp->count < flist->count)
  393.             flist = tmp;
  394.         else if (flist->count >= File_Count_Threshold)
  395.         {
  396.             log ("fdb_search: token \"%s\" contains %d files",
  397.                  flist->key, flist->count);
  398.         }
  399.  
  400.         /* keep track of the list of search terms to match.  we use this
  401.          * later to ensure that all of these tokens appear in the files we
  402.          * are considering as possible matches
  403.          */
  404.         list = CALLOC (1, sizeof (LIST));
  405.         if (!list)
  406.         {
  407.             OUTOFMEMORY ("fdb_search");
  408.         }
  409.         else
  410.         {
  411.             list->data = tmp;   /* current word */
  412.             words = list_push (words, list);
  413.         }
  414.     }
  415.  
  416.     /* find the list of words to exclude, if any */
  417.     for (ptok = excludes; ptok; ptok = ptok->next)
  418.     {
  419.         tmp = hash_lookup (File_Table, ptok->data);
  420.         if (tmp)
  421.         {
  422.             list = CALLOC (1, sizeof (LIST));
  423.             list->data = tmp;
  424.             exclude_words = list_push (exclude_words, list);
  425.         }
  426.     }
  427.  
  428.     /* find the list of files which contain all search tokens.  we do this
  429.      * by iterating the smallest list of files from each of the matched
  430.      * search terms.  for each file in that list, ensure the file is a member
  431.      * of each of the other lists as well
  432.      */
  433.     for (ptok = flist->list; ptok; ptok = ptok->next)
  434.     {
  435.         /* current file to match */
  436.         d = (DATUM *) ptok->data;
  437.  
  438.         /* make sure each search token listed in `words' is present for
  439.          * each member of this list.  i am assuming the number of search
  440.          * tokens is smaller than the number of tokens for a given file.
  441.          * each element of `words' is an FLIST containing all the matching
  442.          * files
  443.          */
  444.         is_match = 1;
  445.         for (pWords = words; pWords; pWords = pWords->next)
  446.         {
  447.             /* each DATUM contains a list of all the tokens it contains.
  448.              * check to make sure the current search term is a member
  449.              * of the list.  skip the word we are matching on since we
  450.              * know its there.
  451.              */
  452.             if (pWords->data != d && list_find (d->tokens, pWords->data) == 0)
  453.             {
  454.                 is_match = 0;
  455.                 break;
  456.             }
  457.         }
  458.  
  459.         if (!is_match)
  460.             continue;
  461.  
  462.         /* check to make sure this file doesn't contain any of the excluded
  463.          * words
  464.          */
  465.         for (pWords = exclude_words; pWords; pWords = pWords->next)
  466.         {
  467.             if (list_find (d->tokens, pWords->data))
  468.             {
  469.                 /* file contains a bad word */
  470.                 is_match = 0;
  471.                 break;
  472.             }
  473.         }
  474.  
  475.         if (!is_match)
  476.             continue;
  477.  
  478.         /* don't return matches for a user's own files */
  479.         if (d->user == crit->user)
  480.         {
  481.             continue;
  482.         }
  483.  
  484.         /* ignore match if both parties are firewalled */
  485.  
  486.         if (crit->user->port == 0 && d->user->port == 0)
  487.         {
  488.             continue;
  489.         }
  490.         if (BitRate[d->bitrate] < crit->minbitrate)
  491.         {
  492.             continue;
  493.         }
  494.         if (BitRate[d->bitrate] > crit->maxbitrate)
  495.         {
  496.             continue;
  497.         }
  498.         if (d->user->speed < crit->minspeed)
  499.         {
  500.             continue;
  501.         }
  502.         if (d->user->speed > crit->maxspeed)
  503.         {
  504.             continue;
  505.         }
  506.         if (d->size < crit->minsize)
  507.         {
  508.             continue;
  509.         }
  510.         if (d->size > crit->maxsize)
  511.         {
  512.             continue;
  513.         }
  514.         if (d->duration < crit->minduration)
  515.         {
  516.             continue;
  517.         }
  518.         if (d->duration > crit->maxduration)
  519.         {
  520.             continue;
  521.         }
  522.         if (SampleRate[d->frequency] < crit->minfreq)
  523.         {
  524.             continue;
  525.         }
  526.         if (SampleRate[d->frequency] > crit->maxfreq)
  527.         {
  528.             continue;
  529.         }
  530.         if (crit->type != -1 && crit->type != d->type)
  531.         {
  532.             continue;           /* wrong content type */
  533.         }
  534.         /* send the result to the server that requested it */
  535.         if (crit->id)
  536.         {
  537.             ASSERT (ISSERVER (crit->con));
  538.             ASSERT (validate_user (d->user));
  539.             /* 10016 <id> <user> "<filename>" <md5> <size> <bitrate> <frequency> <duration> */
  540.             send_cmd (crit->con, MSG_SERVER_REMOTE_SEARCH_RESULT,
  541.                       "%s %s \"%s\" %s %u %d %d %d",
  542.                       crit->id, d->user->nick, d->filename,
  543. #if RESUME
  544.                       d->hash,
  545. #else
  546.                       "00000000000000000000000000000000",
  547. #endif
  548.                       d->size, BitRate[d->bitrate],
  549.                       SampleRate[d->frequency], d->duration);
  550.         }
  551.         /* if a local user issued the search, notify them of the match */
  552.         else
  553.         {
  554.             send_cmd (crit->con, MSG_SERVER_SEARCH_RESULT,
  555.                       "\"%s\" %s %u %d %d %d %s %u %d", d->filename,
  556. #if RESUME
  557.                       d->hash,
  558. #else
  559.                       "00000000000000000000000000000000",
  560. #endif
  561.                       d->size,
  562.                       BitRate[d->bitrate],
  563.                       SampleRate[d->frequency],
  564.                       d->duration,
  565.                       d->user->nick, d->user->ip, d->user->speed);
  566.         }
  567.  
  568.         /* filename matches, check other criteria */
  569.         if (++hits == maxhits)
  570.             break;
  571.     }
  572.  
  573.     list_free (words, 0);
  574.  
  575.     return hits;
  576. }
  577.  
  578. static void
  579. generate_qualifier (char *d, int dsize, char *attr, unsigned int min,
  580.                     unsigned int max, unsigned int hardmax)
  581. {
  582.     if (min > 0)
  583.         snprintf (d, dsize, " %s \"%s\" %d",
  584.                   attr, (min == max) ? "EQUAL TO" : "AT LEAST", min);
  585.     else if (max < hardmax)
  586.         snprintf (d, dsize, " %s \"AT BEST\" %d", attr, max);
  587. }
  588.  
  589. #define MAX_SPEED 10
  590. #define MAX_BITRATE 0xffff
  591. #define MAX_FREQUENCY 0xffff
  592. #define MAX_DURATION 0xffff
  593. #define MAX_SIZE 0xffffffff
  594.  
  595. static void
  596. generate_request (char *d, int dsize, int results, LIST * contains,
  597.                   LIST * excludes, SEARCH * parms)
  598. {
  599.     int     l;
  600.  
  601.     snprintf (d, dsize, "FILENAME CONTAINS \"");
  602.     l = strlen (d);
  603.     d += l;
  604.     dsize -= l;
  605.     for (; contains; contains = contains->next)
  606.     {
  607.         snprintf (d, dsize, "%s ", (char *) contains->data);
  608.         l = strlen (d);
  609.         d += l;
  610.         dsize -= l;
  611.     }
  612.     snprintf (d, dsize, "\" MAX_RESULTS %d", results);
  613.     l = strlen (d);
  614.     d += l;
  615.     dsize -= l;
  616.     if (parms->type != CT_MP3)
  617.     {
  618.         snprintf (d, dsize, " TYPE %s",
  619.                   parms->type != -1 ? Content_Types[parms->type] : "ANY");
  620.         l = strlen (d);
  621.         d += l;
  622.         dsize -= l;
  623.     }
  624.     generate_qualifier (d, dsize, "BITRATE", parms->minbitrate,
  625.                         parms->maxbitrate, MAX_BITRATE);
  626.     l = strlen (d);
  627.     d += l;
  628.     dsize -= l;
  629.     generate_qualifier (d, dsize, "FREQ", parms->minfreq, parms->maxfreq,
  630.                         MAX_FREQUENCY);
  631.     l = strlen (d);
  632.     d += l;
  633.     dsize -= l;
  634.     generate_qualifier (d, dsize, "LINESPEED", parms->minspeed,
  635.                         parms->maxspeed, MAX_SPEED);
  636.     l = strlen (d);
  637.     d += l;
  638.     dsize -= l;
  639.     generate_qualifier (d, dsize, "SIZE", parms->minsize,
  640.                         parms->maxsize, MAX_SIZE);
  641.     l = strlen (d);
  642.     d += l;
  643.     dsize -= l;
  644.     generate_qualifier (d, dsize, "DURATION", parms->minduration,
  645.                         parms->maxduration, MAX_DURATION);
  646.     l = strlen (d);
  647.     d += l;
  648.     dsize -= l;
  649.  
  650.     if (excludes)
  651.     {
  652.         snprintf (d, dsize, " FILENAME EXCLUDES \"");
  653.         l = strlen (d);
  654.         d += l;
  655.         dsize -= l;
  656.         for (; excludes; excludes = excludes->next)
  657.         {
  658.             snprintf (d, dsize, "%s ", (char *) excludes->data);
  659.             l = strlen (d);
  660.             d += l;
  661.             dsize -= l;
  662.         }
  663.         snprintf (d, dsize, "\"");
  664.         l = strlen (d);
  665.         d += l;
  666.         dsize -= l;
  667.     }
  668. }
  669.  
  670. static int
  671. set_compare (CONNECTION * con, const char *op, int val, int *min, int *max)
  672. {
  673.     ASSERT (validate_connection (con));
  674.     ASSERT (min != NULL);
  675.     ASSERT (max != NULL);
  676.     if (!strcasecmp (op, "equal to"))
  677.         *min = *max = val;
  678.     else if (!strcasecmp (op, "at least"))
  679.         *min = val;
  680.     else if (!strcasecmp (op, "at best"))
  681.         *max = val;
  682.     else if (ISUSER (con))
  683.     {
  684.         send_cmd (con, MSG_SERVER_NOSUCH, "%s: invalid comparison for search",
  685.                   op);
  686.         return 1;
  687.     }
  688.     return 0;
  689. }
  690.  
  691. /* common code for local and remote searching */
  692. static void
  693. search_internal (CONNECTION * con, USER * user, char *id, char *pkt)
  694. {
  695.     int     i, n, max_results = Max_Search_Results, done = 1, local = 0;
  696.     int     invalid = 0;
  697.     LIST   *contains = 0;
  698.     LIST   *excludes = 0;
  699.     SEARCH  parms;
  700.     char   *arg, *arg1, *ptr;
  701.  
  702.     ASSERT (validate_connection (con));
  703.  
  704.     /* set defaults */
  705.     memset (&parms, 0, sizeof (parms));
  706.     parms.con = con;
  707.     parms.user = user;
  708.     parms.maxspeed = MAX_SPEED;
  709.     parms.maxbitrate = MAX_BITRATE;
  710.     parms.maxfreq = MAX_FREQUENCY;
  711.     parms.maxsize = MAX_SIZE;
  712.     parms.maxduration = MAX_DURATION;
  713.     parms.type = CT_MP3;        /* search for audio/mp3 by default */
  714.     parms.id = id;
  715.  
  716.     /* prime the first argument */
  717.     arg = next_arg (&pkt);
  718.     while (arg)
  719.     {
  720.         if (!strcasecmp ("filename", arg))
  721.         {
  722.             arg = next_arg (&pkt);
  723.             arg1 = next_arg (&pkt);
  724.             if (!arg || !arg1)
  725.             {
  726.                 invalid = 1;
  727.                 goto done;
  728.             }
  729.             /* do an implicit AND operation if multiple FILENAME CONTAINS
  730.                clauses are specified */
  731.             if (!strcasecmp ("contains", arg))
  732.                 contains = list_append (contains, tokenize (arg1, &excludes));
  733.             else if (!strcasecmp ("excludes", arg))
  734.                 /* ignore `-' prefix here */
  735.                 excludes = list_append (excludes, tokenize (arg1, NULL));
  736.             else
  737.             {
  738.                 invalid = 1;
  739.                 goto done;
  740.             }
  741.         }
  742.         else if (!strcasecmp ("max_results", arg))
  743.         {
  744.             arg = next_arg (&pkt);
  745.             if (!arg)
  746.             {
  747.                 invalid = 1;
  748.                 goto done;
  749.             }
  750.             max_results = strtol (arg, &ptr, 10);
  751.             if (*ptr)
  752.             {
  753.                 /* not a number */
  754.                 invalid = 1;
  755.                 goto done;
  756.             }
  757.             if (Max_Search_Results > 0 && max_results > Max_Search_Results)
  758.                 max_results = Max_Search_Results;
  759.         }
  760.         else if (!strcasecmp ("type", arg))
  761.         {
  762.             arg = next_arg (&pkt);
  763.             if (!arg)
  764.             {
  765.                 invalid = 1;
  766.                 goto done;
  767.             }
  768.             parms.type = -1;
  769.             if (strcasecmp ("any", arg))
  770.             {
  771.                 for (n = CT_MP3; n < CT_UNKNOWN; n++)
  772.                 {
  773.                     if (!strcasecmp (arg, Content_Types[n]))
  774.                     {
  775.                         parms.type = n;
  776.                         break;
  777.                     }
  778.                 }
  779.                 if (parms.type == -1)
  780.                 {
  781.                     if (ISUSER (con))
  782.                         send_cmd (con, MSG_SERVER_NOSUCH,
  783.                                   "%s: invalid type for search", arg);
  784.                     goto done;
  785.                 }
  786.             }
  787.         }
  788.         else if ((!strcasecmp ("linespeed", arg) && (i = 1)) ||
  789.                  (!strcasecmp ("bitrate", arg) && (i = 2)) ||
  790.                  (!strcasecmp ("freq", arg) && (i = 3)) ||
  791.                  (!strcasecmp ("size", arg) && (i = 4)) ||
  792.                  (!strcasecmp ("duration", arg) && (i = 5)))
  793.         {
  794.             int    *min, *max;
  795.  
  796.             arg = next_arg (&pkt);      /* comparison operation */
  797.             arg1 = next_arg (&pkt);     /* value */
  798.             if (!arg || !arg1)
  799.             {
  800.                 invalid = 1;
  801.                 goto done;
  802.             }
  803.             n = strtol (arg1, &ptr, 10);
  804.             if (*ptr)
  805.             {
  806.                 /* not a number */
  807.                 invalid = 1;
  808.                 goto done;
  809.             }
  810.             if (i == 1)
  811.             {
  812.                 min = &parms.minspeed;
  813.                 max = &parms.maxspeed;
  814.             }
  815.             else if (i == 2)
  816.             {
  817.                 min = &parms.minbitrate;
  818.                 max = &parms.maxbitrate;
  819.             }
  820.             else if (i == 3)
  821.             {
  822.                 min = &parms.minfreq;
  823.                 max = &parms.maxfreq;
  824.             }
  825.             else if (i == 4)
  826.             {
  827.                 min = (int *) &parms.minsize;
  828.                 max = (int *) &parms.maxsize;
  829.             }
  830.             else if (i == 5)
  831.             {
  832.                 min = &parms.minduration;
  833.                 max = &parms.maxduration;
  834.             }
  835.             else
  836.             {
  837.                 log ("fdb_search: ERROR, drscholl fucked up if you see this");
  838.                 goto done;
  839.             }
  840.  
  841.             if (set_compare (con, arg, n, min, max))
  842.                 goto done;
  843.         }
  844.         else if (!strcasecmp ("local", arg)
  845.                  || !strcasecmp ("local_only", arg))
  846.         {
  847.             local = 1;          /* only search for files from users on the same server */
  848.         }
  849.         else
  850.         {
  851.             log ("search: %s: unknown search argument", arg);
  852.             invalid = 1;
  853.             goto done;
  854.         }
  855.         arg = next_arg (&pkt);  /* skip to next token */
  856.     }
  857.  
  858.     if (!contains)
  859.     {
  860.         if (ISUSER (con))
  861.             send_cmd (con, MSG_SERVER_NOSUCH,
  862.                       "search failed: request contained no valid words");
  863.         goto done;
  864.     }
  865.  
  866.     n = fdb_search (contains, excludes, max_results, &parms);
  867.  
  868.     if ((n < max_results) && !local &&
  869.         ((ISSERVER (con) && list_count (Servers) > 1) ||
  870.          (ISUSER (con) && Servers)))
  871.     {
  872.         char   *request;
  873.  
  874.         /* reform the search request to send to the remote servers */
  875.         generate_request (Buf, sizeof (Buf), max_results - n, contains,
  876.                           excludes, &parms);
  877.         /* make a copy since pass_message_args() uses Buf[] */
  878.         request = STRDUP (Buf);
  879.  
  880.         if (dsearch_alloc (con, user, id, request))
  881.         {
  882.             FREE (request);
  883.             goto done;
  884.         }
  885.  
  886.         FREE (request);
  887.         done = 0;               /* delay sending the end-of-search message */
  888.     }
  889.  
  890.   done:
  891.  
  892.     if (invalid)
  893.     {
  894.         if (ISUSER (con))
  895.             send_cmd (con, MSG_SERVER_NOSUCH, "invalid search request");
  896.     }
  897.  
  898.     list_free (contains, 0);
  899.     list_free (excludes, 0);
  900.  
  901.     if (done)
  902.         search_end (con, id);
  903. }
  904.  
  905. /* 200 ... */
  906. HANDLER (search)
  907. {
  908.     (void) tag;
  909.     (void) len;
  910.     ASSERT (validate_connection (con));
  911.     CHECK_USER_CLASS ("search");
  912.  
  913.     /* if Max_Searches is > 0, we only allow clients to have a certain small
  914.      * number of pending search requests.  Some abusive clients will tend
  915.      * to issues multiple search requests at a time.
  916.      */
  917.     if (con->uopt->searches < 0)
  918.     {
  919.         log ("search: ERROR, con->uopt->searches < 0!!!");
  920.         send_cmd (con, MSG_SERVER_NOSUCH, "search failed: server error");
  921.         con->uopt->searches = 0;
  922.         return;
  923.     }
  924.  
  925.     /* if Max_Searches is > 0, we only allow clients to have a certain small
  926.      * number of pending search requests.  Some abusive clients will tend
  927.      * to issues multiple search requests at a time.
  928.      */
  929.     if (Max_Searches > 0 && con->uopt->searches >= Max_Searches)
  930.     {
  931.         send_cmd (con, MSG_SERVER_NOSUCH,
  932.                   "search failed: too many pending searches");
  933.         return;
  934.     }
  935.  
  936.     if (con->uopt->searches == 0x7fffffff)
  937.     {
  938.         log ("search: ERROR, con->uopt->searches will overflow!!!");
  939.         send_cmd (con, MSG_SERVER_NOSUCH, "search failed: server error");
  940.         return;
  941.     }
  942.     con->uopt->searches++;
  943.     search_internal (con, con->user, 0, pkt);
  944. }
  945. #endif /* ! ROUTING_ONLY */
  946.  
  947. static DSEARCH *
  948. find_search (const char *id)
  949. {
  950.     LIST   *list;
  951.     DSEARCH *ds;
  952.  
  953.     for (list = Remote_Search; list; list = list->next)
  954.     {
  955.         ASSERT (list->data != 0);
  956.         ds = list->data;
  957.         if (!strcmp (ds->id, id))
  958.             return ds;
  959.     }
  960.     return 0;
  961. }
  962.  
  963. /* 10015 <sender> <id> ...
  964.    remote search request */
  965. HANDLER (remote_search)
  966. {
  967.     USER   *user;
  968.     char   *nick, *id;
  969.  
  970.     (void) tag;
  971.     (void) len;
  972.     ASSERT (validate_connection (con));
  973.     CHECK_SERVER_CLASS ("remote_search");
  974.     nick = next_arg (&pkt);     /* user that issued the search */
  975.     id = next_arg (&pkt);
  976.     if (!nick || !id || !pkt)
  977.     {
  978.         /* try to terminate the search anyway */
  979.         if (id)
  980.             send_cmd (con, MSG_SERVER_REMOTE_SEARCH_END, "%s", id);
  981.         log ("remote_search: too few parameters");
  982.         return;
  983.     }
  984.     user = hash_lookup (Users, nick);
  985.     if (!user)
  986.     {
  987.         log ("remote_search: could not locate user %s (from %s)", nick,
  988.              con->host);
  989.         /* imediately notify the peer that we don't have any matches */
  990.         send_cmd (con, MSG_SERVER_REMOTE_SEARCH_END, "%s", id);
  991.         return;
  992.     }
  993. #ifdef ROUTING_ONLY
  994.     Search_Count++;
  995.     /* no local files, just pass this request to the peer servers and
  996.      * wait for the reponses
  997.      */
  998.     if (dsearch_alloc (con, user, id, pkt))
  999.     {
  1000.         /* failed, send the ACK back immediately */
  1001.         send_cmd (con, MSG_SERVER_REMOTE_SEARCH_END, "%s", id);
  1002.     }
  1003. #else
  1004.     search_internal (con, user, id, pkt);
  1005. #endif
  1006. }
  1007.  
  1008. /* 10016 <id> <user> "<filename>" <md5> <size> <bitrate> <frequency> <duration>
  1009.    send a search match to a remote user */
  1010. HANDLER (remote_search_result)
  1011. {
  1012.     DSEARCH *search;
  1013.     char   *av[8];
  1014.     int     ac;
  1015.     USER   *user;
  1016.  
  1017.     (void) con;
  1018.     (void) tag;
  1019.     (void) len;
  1020.     ASSERT (validate_connection (con));
  1021.     CHECK_SERVER_CLASS ("remote_search_result");
  1022.     ac = split_line (av, sizeof (av) / sizeof (char *), pkt);
  1023.  
  1024.     if (ac != 8)
  1025.     {
  1026.         log ("remote_search_result: wrong number of args");
  1027.         print_args (ac, av);
  1028.         return;
  1029.     }
  1030.     search = find_search (av[0]);
  1031.     if (!search)
  1032.     {
  1033.         log ("remote_search_result: could not find search id %s", av[0]);
  1034.         return;
  1035.     }
  1036.     if (ISUSER (search->con))
  1037.     {
  1038.         /* deliver the match to the client */
  1039.         user = hash_lookup (Users, av[1]);
  1040.         if (!user)
  1041.         {
  1042.             log ("remote_search_result: could not find user %s (from %s)",
  1043.                  av[1], con->host);
  1044.             return;
  1045.         }
  1046.         send_cmd (search->con, MSG_SERVER_SEARCH_RESULT,
  1047.                   "\"%s\" %s %s %s %s %s %s %u %d",
  1048.                   av[2], av[3], av[4], av[5], av[6], av[7], user->nick,
  1049.                   user->ip, user->speed);
  1050.     }
  1051.     else
  1052.     {
  1053.         /* pass the message back to the server we got the request from */
  1054.         ASSERT (ISSERVER (search->con));
  1055.         /* should not send it back to the server we just recieved it from */
  1056.         ASSERT (con != search->con);
  1057.         send_cmd (search->con, tag, "%s %s \"%s\" %s %s %s %s %s",
  1058.                   av[0], av[1], av[2], av[3], av[4], av[5], av[6], av[7]);
  1059.     }
  1060. }
  1061.  
  1062. /* consolodated code for removing a pending search struct from the list.
  1063.  * this needs to be done from several points, so aggregate the command code
  1064.  * here.  Note that *list gets updated, so its perfectly fine to loop on
  1065.  * it when calling this routine.
  1066.  */
  1067. static void
  1068. unlink_search (LIST ** list, int send_ack)
  1069. {
  1070.     DSEARCH *s = (*list)->data;
  1071.     LIST   *tmp;
  1072.  
  1073.     ASSERT (validate_connection (s->con));
  1074.     if (send_ack)
  1075.         search_end (s->con, s->id);
  1076.     free_dsearch (s);
  1077.     tmp = *list;
  1078.     *list = (*list)->next;
  1079.     /* if there are no more entries in the list, we have to update the
  1080.      * tail pointer
  1081.      */
  1082.     if (!*list)
  1083.         Remote_Search_Tail = list;
  1084.     FREE (tmp);
  1085.  
  1086.     if (Pending_Searches == 0)
  1087.         log ("search_end: ERROR, Pending_Searches == 0!!!");
  1088.     else
  1089.         Pending_Searches--;
  1090. }
  1091.  
  1092. /* 10017 <id>
  1093.    indicates end of search results for <id> */
  1094. HANDLER (remote_search_end)
  1095. {
  1096.     DSEARCH *search;
  1097.     LIST  **list;
  1098.     char   *id = next_arg (&pkt);
  1099.  
  1100.     ASSERT (validate_connection (con));
  1101.     (void) con;
  1102.     (void) tag;
  1103.     (void) len;
  1104.  
  1105.     list = &Remote_Search;
  1106.     while (*list)
  1107.     {
  1108.         if (!strcmp (((DSEARCH *) (*list)->data)->id, id))
  1109.             break;
  1110.         list = &(*list)->next;
  1111.     }
  1112.     if (!*list)
  1113.     {
  1114.         log ("remote_end_match: could not find entry for search id %s", id);
  1115.         return;
  1116.     }
  1117.     search = (*list)->data;
  1118.     ASSERT (search->numServers <= list_count (Servers));
  1119.     search->count++;
  1120.     if (search->count == search->numServers)
  1121.     {
  1122.         /* got the end of the search matches from all our peers, clean up */
  1123.         unlink_search (list, 1);
  1124.     }
  1125. }
  1126.  
  1127. /* if a user logs out before the search is complete, we need to cancel
  1128.    the search so that we don't try to send the result to the client */
  1129. void
  1130. cancel_search (CONNECTION * con)
  1131. {
  1132.     LIST  **list;
  1133.     DSEARCH *d;
  1134.     int     isServer = ISSERVER (con);
  1135.  
  1136.     ASSERT (validate_connection (con));
  1137.     list = &Remote_Search;
  1138.     while (*list)
  1139.     {
  1140.         d = (*list)->data;
  1141.         if (isServer)
  1142.             d->numServers--;
  1143.         if (d->con == con || d->count >= d->numServers)
  1144.             /* this call updates *list, so we don't have to worry about an
  1145.              * inifinite loop
  1146.              */
  1147.             unlink_search (list, (d->con != con));
  1148.         else
  1149.             list = &(*list)->next;
  1150.     }
  1151. }
  1152.  
  1153. void
  1154. expire_searches (void)
  1155. {
  1156.     LIST  **list = &Remote_Search;
  1157.     DSEARCH *search;
  1158.     int     expired = 0;
  1159.  
  1160.     while (*list)
  1161.     {
  1162.         search = (*list)->data;
  1163.         if (search->timestamp + Search_Timeout > global.current_time)
  1164.             break;              /* everything else in the list is older, so we
  1165.                                    can safely stop here */
  1166.         /* this call updates *list, so we don't have to worry about an
  1167.          * inifinite loop
  1168.          */
  1169.         unlink_search (list, 1);
  1170.         expired++;
  1171.     }
  1172.     if (expired)
  1173.         log ("expire_searches: %d stale entries", expired);
  1174. }
  1175.